#include <linux/err.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/fs2.h>
#include <linux/binfmts.h>

#include <asm/mmu_context.h>

static int exec_mmap(struct mm_struct *mm)
{
	struct task_struct *tsk;
	struct mm_struct *old_mm, *active_mm;

	printf("this is %s(): %d\r\n", __func__, __LINE__);

	mm->context.id = 0;

	/* Notify parent that we're no longer interested in the old VM */
	tsk = current;
	old_mm = current->mm;
	// mm_release(tsk, old_mm);

	// if (old_mm) {
	// 	printf("this is %s(): current->mm is NOT null\r\n", __func__, __LINE__);
	// 	return 0;
	// }

	// if (old_mm) {
	// 	/*
	// 	 * Make sure that if there is a core dump in progress
	// 	 * for the old mm, we get out and die instead of going
	// 	 * through with the exec.  We must hold mmap_sem around
	// 	 * checking core_state and changing tsk->mm.
	// 	 */
	// 	down_read(&old_mm->mmap_sem);
	// 	if (unlikely(old_mm->core_state)) {
	// 		up_read(&old_mm->mmap_sem);
	// 		return -EINTR;
	// 	}
	// }
	// task_lock(tsk);
	active_mm = tsk->active_mm;
	tsk->mm = mm;
	tsk->active_mm = mm;
	activate_mm(active_mm, mm);
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	// task_unlock(tsk);
	// arch_pick_mmap_layout(mm);
	// if (old_mm) {
	// 	up_read(&old_mm->mmap_sem);
	// 	BUG_ON(active_mm != old_mm);
	// 	mm_update_next_owner(old_mm);
	// 	mmput(old_mm);
	// 	return 0;
	// }
	// mmdrop(active_mm);
	return 0;
}

void set_task_comm(struct task_struct *tsk, char *buf)
{
	// task_lock(tsk);
	strncpy(tsk->comm, buf, sizeof(tsk->comm));
	// task_unlock(tsk);
	// perf_event_comm(tsk);
}

int flush_old_exec(struct linux_binprm * bprm)
{
	char * name;
	int i, ch, retval;
	char tcomm[sizeof(current->comm)];

	printf("this is %s(): %d\r\n", __func__, __LINE__);

	/*
	 * Make sure we have a private signal table and that
	 * we are unassociated from the previous thread group.
	 */
	// retval = de_thread(current);
	// if (retval)
	// 	goto out;

	// set_mm_exe_file(bprm->mm, bprm->file);

	/*
	 * Release all of the old mmap stuff
	 */
	retval = exec_mmap(bprm->mm);
	if (retval)
		goto out;

	printf("this is %s(): %d\r\n", __func__, __LINE__);
	bprm->mm = NULL;		/* We're using it now */

	/* This is the point of no return */
	// current->sas_ss_sp = current->sas_ss_size = 0;

	// if (current_euid() == current_uid() && current_egid() == current_gid())
		// set_dumpable(current->mm, 1);
	// else
		// set_dumpable(current->mm, suid_dumpable);

	name = bprm->filename;
	printf("this is %s(): %d\r\n", __func__, __LINE__);

	/* Copies the binary name from after last slash */
	for (i=0; (ch = *(name++)) != '\0';) {
		if (ch == '/')
			i = 0; /* overwrite what we wrote */
		else
			if (i < (sizeof(tcomm) - 1))
				tcomm[i++] = ch;
	}
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	tcomm[i] = '\0';
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	set_task_comm(current, tcomm);
	printf("this is %s(): %d\r\n", __func__, __LINE__);

	// current->flags &= ~PF_RANDOMIZE;
	// flush_thread();

	/* Set the new mm task size. We have to do that late because it may
	 * depend on TIF_32BIT which is only updated in flush_thread() on
	 * some architectures like powerpc
	 */
	current->mm->task_size = TASK_SIZE;

	/* install the new credentials */
	// if (bprm->cred->uid != current_euid() ||
	//     bprm->cred->gid != current_egid()) {
	// 	current->pdeath_signal = 0;
	// } else if (file_permission(bprm->file, MAY_READ) ||
	// 	   bprm->interp_flags & BINPRM_FLAGS_ENFORCE_NONDUMP) {
	// 	set_dumpable(current->mm, suid_dumpable);
	// }

	// current->personality &= ~bprm->per_clear;

	/*
	 * Flush performance counters when crossing a
	 * security domain:
	 */
	// if (!get_dumpable(current->mm))
	// 	perf_event_exit_task(current);

	/* An exec changes our domain. We are no longer part of the thread
	   group */

	// current->self_exec_id++;
			
	// flush_signal_handlers(current, 0);
	// flush_old_files(current->files);
	printf("this is %s(): %d\r\n", __func__, __LINE__);

	return 0;

out:
	return retval;
}

int count(char **argv, int max)
{
	int i = 0;
	char **tmp = argv;

	if (tmp != NULL) {
		for (;;) {
			char *p;

			// if (get_user(p, argv))
			// 	return -EFAULT;
			p = *tmp;
			if (!p)
				break;
			tmp++;
			if (i++ >= max)
				return -E2BIG;
			// cond_resched();
		}
	}
	return i;
}

struct file *open_exec(const char *name)
{
	struct file *file;
	int err;

	// file = do_filp_open(AT_FDCWD, name,
	// 			O_LARGEFILE | O_RDONLY | FMODE_EXEC, 0,
	// 			MAY_EXEC | MAY_OPEN);
	file = do_filp_open(0, name, 0, 0, 0);
	if (IS_ERR(file))
		goto out;

	// err = -EACCES;
	// if (!S_ISREG(file->f_path.dentry->d_inode->i_mode))
	// 	goto exit;

	// if (file->f_path.mnt->mnt_flags & MNT_NOEXEC)
	// 	goto exit;

	// fsnotify_open(file->f_path.dentry);

	// err = deny_write_access(file);
	// if (err)
	// 	goto exit;

out:
	return file;

exit:
	// fput(file);
	return ERR_PTR(err);
}

static int __bprm_mm_init(struct linux_binprm *bprm)
{
	int err;
	struct vm_area_struct *vma = NULL;
	struct mm_struct *mm = bprm->mm;

	// bprm->vma = vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);
	bprm->vma = vma = kmalloc(sizeof(struct vm_area_struct));
	if (!vma)
		return -ENOMEM;

	// down_write(&mm->mmap_sem);
	vma->vm_mm = mm;

	/*
	 * Place the stack at the largest stack address the architecture
	 * supports. Later, we'll move this to an appropriate place. We don't
	 * use STACK_TOP because that can depend on attributes which aren't
	 * configured yet.
	 */
	vma->vm_end = STACK_TOP_MAX;
	vma->vm_start = vma->vm_end - PAGE_SIZE;
	// vma->vm_flags = VM_STACK_FLAGS;
	// vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
	err = insert_vm_struct(mm, vma);
	if (err)
		goto err;

	mm->stack_vm = mm->total_vm = 1;
	// up_write(&mm->mmap_sem);
	bprm->p = vma->vm_end - sizeof(void *);
	return 0;
err:
	// up_write(&mm->mmap_sem);
	bprm->vma = NULL;
	// kmem_cache_free(vm_area_cachep, vma);
	return err;
}

int bprm_mm_init(struct linux_binprm *bprm)
{
	int err;
	struct mm_struct *mm = NULL;

	bprm->mm = mm = mm_alloc();
	err = -ENOMEM;
	if (!mm)
		goto err;

	// err = init_new_context(current, mm);
	// if (err)
	// 	goto err;

	// err = __bprm_mm_init(bprm);
	// if (err)
	// 	goto err;

	return 0;

err:
	if (mm) {
		bprm->mm = NULL;
		// mmdrop(mm);
	}

	return err;
}

int kernel_read(struct file *file, loff_t offset,
		char *addr, unsigned long count)
{
	// mm_segment_t old_fs;
	loff_t pos = offset;
	int result;

	// old_fs = get_fs();
	// set_fs(get_ds());
	/* The cast to a user pointer is valid due to the set_fs() */
	result = vfs_read(file, (void *)addr, count, &pos);
	// set_fs(old_fs);
	return result;
}

int prepare_binprm(struct linux_binprm *bprm)
{
	// umode_t mode;
	// struct inode * inode = bprm->file->f_path.dentry->d_inode;
	// int retval;

	// mode = inode->i_mode;
	// if (bprm->file->f_op == NULL)
	// 	return -EACCES;

	// /* clear any previous set[ug]id data from a previous binary */
	// bprm->cred->euid = current_euid();
	// bprm->cred->egid = current_egid();

	// if (!(bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID)) {
	// 	/* Set-uid? */
	// 	if (mode & S_ISUID) {
	// 		bprm->per_clear |= PER_CLEAR_ON_SETID;
	// 		bprm->cred->euid = inode->i_uid;
	// 	}

	// 	/* Set-gid? */
	// 	/*
	// 	 * If setgid is set but no group execute bit then this
	// 	 * is a candidate for mandatory locking, not a setgid
	// 	 * executable.
	// 	 */
	// 	if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) {
	// 		bprm->per_clear |= PER_CLEAR_ON_SETID;
	// 		bprm->cred->egid = inode->i_gid;
	// 	}
	// }

	// /* fill in binprm security blob */
	// retval = security_bprm_set_creds(bprm);
	// if (retval)
	// 	return retval;
	// bprm->cred_prepared = 1;

	memset(bprm->buf, 0, BINPRM_BUF_SIZE);
	return kernel_read(bprm->file, 0, bprm->buf, BINPRM_BUF_SIZE);
}

int do_execve(char * filename,
	char **argv,
	char **envp,
	struct pt_regs * regs)
{
	struct linux_binprm *bprm;
	struct file *file;
	// struct files_struct *displaced;
	// bool clear_in_exec;
	int retval;

	printf("this is %s(): %d\r\n", __func__, __LINE__);

	// retval = unshare_files(&displaced);
	// if (retval)
		// goto out_ret;

	retval = -ENOMEM;
	// bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);
	bprm = kmalloc(sizeof(*bprm));
	if (!bprm)
		goto out_files;

	printf("this is %s(): %d\r\n", __func__, __LINE__);
	// retval = prepare_bprm_creds(bprm);
	// if (retval)
	// 	goto out_free;

	// retval = check_unsafe_exec(bprm);
	// if (retval < 0)
	// 	goto out_free;
	// clear_in_exec = retval;
	// current->in_execve = 1;

	file = open_exec(filename);
	retval = PTR_ERR(file);
	if (IS_ERR(file))
		goto out_unmark;

	// sched_exec();
	printf("this is %s(): %d\r\n", __func__, __LINE__);

	bprm->file = file;
	bprm->filename = filename;
	bprm->interp = filename;

	retval = bprm_mm_init(bprm);
	if (retval)
		goto out_file;
	printf("this is %s(): %d\r\n", __func__, __LINE__);

	// bprm->argc = count(argv, MAX_ARG_STRINGS);
	bprm->argc = 1;
	if ((retval = bprm->argc) < 0)
		goto out;
	printf("this is %s(): %d >>> bprm->argc = %d\r\n", __func__, __LINE__, bprm->argc);

	// bprm->envc = count(envp, MAX_ARG_STRINGS);
	// if ((retval = bprm->envc) < 0)
	// 	goto out;

	retval = prepare_binprm(bprm);
	printf("this is %s(): %d >>> retval = %d\r\n", __func__, __LINE__, retval);
	if (retval < 0)
		goto out;
	printf("this is %s(): %d\r\n", __func__, __LINE__);

	// retval = copy_strings_kernel(1, &bprm->filename, bprm);
	// if (retval < 0)
	// 	goto out;

	// bprm->exec = bprm->p;
	// retval = copy_strings(bprm->envc, envp, bprm);
	// if (retval < 0)
	// 	goto out;

	// retval = copy_strings(bprm->argc, argv, bprm);
	// if (retval < 0)
	// 	goto out;
	bprm->argv = (unsigned long)argv;
	printf("this is %s(): %d >>> argv = %x\r\n", __func__, __LINE__, argv);
	printf("this is %s(): %d >>> bprm->argv = %x\r\n", __func__, __LINE__, bprm->argv);

	current->flags &= ~PF_KTHREAD;
	// retval = search_binary_handler(bprm,regs);
	retval = load_elf_binary(bprm,regs);
	if (retval < 0)
		goto out;
	printf("this is %s(): %d\r\n", __func__, __LINE__);

	// current->stack_start = current->mm->start_stack;

	/* execve succeeded */
	// current->fs->in_exec = 0;
	// current->in_execve = 0;
	// acct_update_integrals(current);
	// free_bprm(bprm);
	// if (displaced)
		// put_files_struct(displaced);
	return retval;

out:
	// if (bprm->mm)
		// mmput (bprm->mm);

out_file:
	// if (bprm->file) {
	// 	allow_write_access(bprm->file);
	// 	fput(bprm->file);
	// }

out_unmark:
	// if (clear_in_exec)
	// 	current->fs->in_exec = 0;
	// current->in_execve = 0;

out_free:
	// free_bprm(bprm);

out_files:
	// if (displaced)
	// 	reset_files_struct(displaced);
out_ret:
	return retval;
}
